﻿#region Description
/*  File Name:      MESceneManager.cs
 *  Author:         Adam Goodchild
 *  
 *  Description:    This class Manages each Scene in the Game.
 *                  It will update and draw everything in the Scene.
 *                  EVENTUALLY, I MAY ADD IN INDIVIDUAL SCENES. EACH SCENE CAN THEN 
 *                  BE LOADED AND TREATED ON ITS OWN.
*/
#endregion

#region Using Statements
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework;
using MinimizeEngine.Entities;
using MinimizeEngine.Collision;
using MinimizeEngine.Components.Render;
using MinimizeEngine.Components;
using MinimizeEngine.Components.Render.Grid_System;
#endregion

namespace MinimizeEngine.Managers
{
    public sealed class MESceneGraphManager : GameComponent
    {
        #region Fields

        /// <summary>
        /// Games Root Entity
        /// </summary>
        private static MEBaseEntity mRootEntity;

        /// <summary>
        /// Master List of all Entities in the Scene Graph
        /// </summary>
        private static Dictionary<Int64, MEBaseEntity> mMasterEntityList;

        /// <summary>
        /// Master List of all Render Components in the Scene Graph
        /// </summary>
        private static List<MEBaseComponent> mMasterComponentList;

        /// <summary>
        /// Bool to determine if Cursor Picking is enabled.
        /// </summary>
        private static bool mPickingEnabled = true;

        #region Picking Fields
        /// <summary>
        /// A List of all Bounding Spheres that we are currently inside with out Mouse.
        /// </summary>
        private static List<string> mListInsideBoundingSpheres = new List<string>();

        /// <summary>
        /// The Entity of the Model we are currently Picking.
        /// </summary>
        private static MEBaseEntity mPickedEntity;

        /// <summary>
        /// A List of all Models that have been Picked in a single Test
        /// </summary>
        private static List<MEBaseEntity> mPickedEntitiesList;

        /// <summary>
        /// Store an Array of Vertices to show which Triangle we have picked.
        /// </summary>
        private static VertexPositionColor[] mPickedTriangle =
        {
            new VertexPositionColor(Vector3.Zero, Color.Magenta),
            new VertexPositionColor(Vector3.Zero, Color.Magenta),
            new VertexPositionColor(Vector3.Zero, Color.Magenta),
        };

        /// <summary>
        /// A Basic Effect to be used when Drawing a Picked Triangle.
        /// </summary>
        private static BasicEffect mTriangleLineEffect;

        /// <summary>
        /// A Vertex Declaration to be used when Drawing a Picked Triangle.
        /// </summary>
        private static VertexDeclaration mTriangleVertexDeclaration;
        #endregion

        #endregion

        #region Properties

        /// <summary>
        /// Games Root Entity
        /// </summary>
        public static MEBaseEntity RootEntity
        {
            get { return mRootEntity; }
        }

        /// <summary>
        /// Set and Get if Cursor Picking is enabled or not.
        /// </summary>
        public static bool EnablePicking
        {
            get { return mPickingEnabled; }
            set { mPickingEnabled = value; }
        }

        /// <summary>
        /// Add an Entity to the SceneGraph
        /// </summary>
        /// <param name="entity"></param>
        public static void AddEntity(MEBaseEntity entity)
        {
            //Add the Entity to the Master List
            mMasterEntityList.Add(entity.ID, entity);

            if(!entity.IsRootEntity)
            {
                //Add the Entity to the Root Entity
                RootEntity.AddChildEntity(entity);
            }
        }

        public static void AddComponent(MEBaseComponent baseComponent)
        {
            //Add the Base Component to the Master List
            mMasterComponentList.Add(baseComponent);
        }

        /// <summary>
        /// Master Render Component List
        /// </summary>
        public static List<MEBaseComponent> MasterComponentList
        {
            get { return mMasterComponentList; }
        }

        /// <summary>
        /// Master EntityList
        /// </summary>
        public static Dictionary<Int64, MEBaseEntity> MasterEntityList
        {
            get { return mMasterEntityList; }
        }

        /// <summary>
        /// Find an Entity by ID
        /// </summary>
        /// <param name="id">ID to search for</param>
        public static MEBaseEntity FindEntity(Int64 id)
        {
            foreach (MEBaseEntity entity in mMasterEntityList.Values)
            {
                if(entity.ID == id)
                {
                    return entity;
                }
            }
            return null;
        }

        /// <summary>
        /// Find an Entity by Unique Name
        /// </summary>
        /// <param name="id">Unique Name to search for</param>
        public static MEBaseEntity FindEntity(string uniqueName)
        {
            foreach (MEBaseEntity entity in mMasterEntityList.Values)
            {
                if (entity.UniqueName == uniqueName)
                {
                    return entity;
                }
            }
            return null;
        }

        /// <summary>
        /// Get the currently picked Entity
        /// </summary>
        public static MEBaseEntity PickedEntity
        {
            get { return mPickedEntity; }
        }

        #endregion

        #region Initialization

        /// <summary>
        /// Construct a new Scene Graph Manager
        /// </summary>
        public MESceneGraphManager(Game game) : base(game)
        {
            //Create the Master Lists
            mMasterEntityList = new Dictionary<Int64, MEBaseEntity>();
            mMasterComponentList = new List<MEBaseComponent>();
            mPickedEntitiesList = new List<MEBaseEntity>();

            //Create the Root Entity
            mRootEntity = new MEBaseEntity("RootEntity", Vector3.Zero, Matrix.Identity, true);
        }

        /// <summary>
        /// Load the Content for all Entities in the Scene Graph
        /// </summary>
        public static void LoadContent()
        {
            //Create the Basic Effect for the Triangle Picking.
            mTriangleLineEffect = new BasicEffect(MEEngineManager.DeviceManager.GraphicsDevice, null);
            mTriangleLineEffect.VertexColorEnabled = true;

            //Create the Vertex Declaration for the Triangle Picking.
            mTriangleVertexDeclaration = new VertexDeclaration(MEEngineManager.DeviceManager.GraphicsDevice, VertexPositionColor.VertexElements);
        }

        /// <summary>
        /// Unload the Content for all Entities in the Scene Graph
        /// </summary>
        public static void UnloadContent()
        {

        }

        #endregion

        #region Update

        /// <summary>
        /// Update all Entities in the Scene Graph
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Update(GameTime gameTime)
        {
            foreach (MEBaseEntity entity in mRootEntity.Children)
            {
                entity.Update(gameTime);
            }
        }

        /// <summary>
        /// Update the Triangle Picking Algorithm.
        /// This runs an algorithm over all Models in the World, and stores which triangle is
        /// under the Mouse Cursor.
        /// </summary>
        public static void UpdateTrianglePicking()
        {
            //Clear list of Picked Entities
            mPickedEntitiesList.Clear();

            //Get the Cursor Ray
            Ray mouseRay = MECameraManager.mActiveCamera.CreateMouseRay();

            //First clear all previous Bounding Spheres we were inside
            mListInsideBoundingSpheres.Clear();

            //We have not selected a Model yet, so set the picked models name to none.
            mPickedEntity = null;

            //If there is more then one Model under the mouse cursor, then we want to only select
            //the closest one. So we shall store the closest Model we have found so far.
            float closestPOI = float.MaxValue;

            //Now loop through all Models in the World
            foreach (MEBaseComponent baseComponent in MasterComponentList)
            {
                if(baseComponent is MERenderComponent)
                {
                    bool inside;
                    Vector3 v1, v2, v3;

                    //First perform the Ray Intersects Model test.
                    float? intersection = MECollisionFunctions.RayIntersectsModel(mouseRay, (MERenderComponent)baseComponent, out inside, out v1, out v2, out v3);

                    //If the Ray intersects the Bounding Sphere of the Model, then we store it
                    //for reference purposes.
                    if (inside)
                        mListInsideBoundingSpheres.Add(baseComponent.Parent.Name);

                    //Check to see if we have an Intersection with Triangles or not.
                    if (intersection != null)
                    {
                        //Check to see if we are closer than the previous intersection
                        if (intersection < closestPOI)
                        {
                            //Store the Information regarding the Model
                            closestPOI = intersection.Value;

                            //Store the Models Name
                            mPickedEntitiesList.Add(baseComponent.Parent);

                            //Store the Triangles Vertex Positions, so that we can display the Triangle we are picking.
                            //mPickedTriangle[0].Position = v1;
                            //mPickedTriangle[1].Position = v2;
                            //mPickedTriangle[2].Position = v3;
                        }
                    }
                    
                    baseComponent.Parent.DrawBoundingSphere = false;
                }  
                else if(baseComponent is MEGridSectionRenderComponent)
                {
                    //Check to see if the Ray Intersects with the Bounding Box.
                    //If it does not, then we do not have to progress onto the more complex check.
                    BoundingBox bb = ((MEGridSectionRenderComponent)baseComponent).Parent.BoundingBox;
                    if (bb.Intersects(mouseRay) == null)
                    {
                        baseComponent.Parent.DrawBoundingBox = false;
                    }
                    else
                    {
                        mPickedEntitiesList.Add(baseComponent.Parent);
                    }
                }
            }

            if (mPickedEntitiesList.Count > 0)
            {
                mPickedEntity = mPickedEntitiesList[0];
                mPickedEntity.DrawBoundingBox = true;
                mPickedEntity.DrawBoundingSphere = true;
            }
            else
            {
                mPickedEntity = null;
            }
        }

        #endregion

        #region Draw

        /// <summary>
        /// Draw all Entities in the Scene Graph
        /// </summary>
        public static void Draw()
        {
            foreach (MEBaseEntity entity in mRootEntity.Children)
            {
                entity.Draw();
            }
        }

        /// <summary>
        /// Helper for drawing the outline of the triangle currently under the cursor.
        /// </summary>
        static void DrawPickedTriangle()
        {
            if (mPickedEntity != null)
            {
                GraphicsDevice device = MEEngineManager.DeviceManager.GraphicsDevice;
                RenderState renderState = device.RenderState;

                // Set line drawing renderstates. We disable backface culling
                // and turn off the depth buffer because we want to be able to
                // see the picked triangle outline regardless of which way it is
                // facing, and even if there is other geometry in front of it.
                renderState.FillMode = FillMode.WireFrame;
                renderState.CullMode = CullMode.None;
                renderState.DepthBufferEnable = false;

                // Activate the line drawing BasicEffect.
                mTriangleLineEffect.Projection = MECameraManager.mActiveCamera.Parent.Projection;
                mTriangleLineEffect.View = MECameraManager.mActiveCamera.Parent.View;

                mTriangleLineEffect.Begin();
                mTriangleLineEffect.CurrentTechnique.Passes[0].Begin();

                // Draw the triangle.
                device.VertexDeclaration = mTriangleVertexDeclaration;

                device.DrawUserPrimitives(PrimitiveType.TriangleList,
                                          mPickedTriangle, 0, 1);

                mTriangleLineEffect.CurrentTechnique.Passes[0].End();
                mTriangleLineEffect.End();

                //Reset the Render States to their default values.
                renderState.FillMode = FillMode.Solid;
                renderState.CullMode = CullMode.CullCounterClockwiseFace;
                renderState.DepthBufferEnable = true;
            }
        }

        #endregion
    }
}
